AI编辑Word文档时如何保留格式
你会学到:用 Claude Code 等 AI 工具编辑已有 Word 文档时,格式经常丢失——字体变了、标题样式没了、表格乱了。本节讲清楚为什么会这样,以及五套实用的格式保留策略(从简单到专业),并对比当前主流的 MCP 工具方案。最后补一条完整交付链路:从公司示例模板出发,经 Markdown 中转生成带题注、交叉引用、域的正式 docx——这是接口文档类交付物的真实工作流。
问题是什么
你有一份排版好的 Word 文档,想让 Claude Code 帮你修改其中几段内容。修改完打开一看:
- 字体从微软雅黑变成了 Calibri
- 标题颜色和字号全变了
- 表格边框和底纹消失了
- 页边距从 2.5cm 变成了默认值
- 页眉页脚不见了
核心矛盾:AI 改的是内容,但格式跟着内容一起被覆盖了。
为什么会丢失格式
Claude Code 编辑 docx 的方式是生成 python-docx 脚本。两个最常见的"格式杀手":
杀手 1:创建新文档而非加载已有文档
# 错误 — 创建一个全新的空白文档,没有任何原有格式
doc = Document()
# 正确 — 加载已有文档,保留所有样式和格式
doc = Document('report.docx')
python
Document() 创建的是基于 Normal 模板的空白文档,字体是 Calibri、页边距是 1 英寸——跟你原来的文档没有任何关系。
杀手 2:整段替换文本而非修改文本片段
# 错误 — 整段文本被替换,段落内每个 run 的独立格式全部丢失
paragraph.text = '新内容'
# 正确 — 只修改 run 的文本,保留每个 run 的字体、颜色、加粗等格式
for run in paragraph.runs:
if '旧内容' in run.text:
run.text = run.text.replace('旧内容', '新内容')
python
Word 文档中,一个段落(Paragraph)由多个"文本片段"(Run)组成,每个 Run 可以有独立的字体、颜色、加粗格式。paragraph.text = ... 会把所有 Run 合并成一个,格式信息全丢。
策略一:提示词明确要求保留格式
最简单的方法:在给 Claude Code 的提示中明确说"保留格式"。
基础提示词模板
编辑 report.docx,要求:
1. 用 Document('report.docx') 加载现有文档,不要用 Document() 创建新文档
2. 只修改我指定的内容,不要动其他部分
3. 保留原始格式(字体、字号、颜色、行距、页边距)
4. 修改文本时,优先修改 run.text,不要用 paragraph.text 整段替换
markdown
更强的约束:放到 CLAUDE.md
把格式规则写到 CLAUDE.md,每次对话自动生效:
## Word 文档编辑规则
- 必须使用 Document('文件路径') 加载已有文档,禁止 Document() 新建
- 修改文本时使用 run.text 替换,禁止 paragraph.text 整段赋值
- 不创建新样式,使用文档中已有的样式
- 不修改页面设置(页边距、纸张大小、页眉页脚)
markdown
原理:CLAUDE.md 在每次对话开始时自动注入到 Claude Code 的上下文中,相当于"默认配置"。不需要每次都重复说。
局限:提示词只能"建议"AI,不能"保证"。AI 可能仍然犯错,尤其面对复杂文档(嵌套表格、多级列表、分节符)时。
策略二:safe-docx MCP Server(推荐)
这是目前专门为 AI Agent 设计的 Word 编辑方案,通过 MCP 协议提供确定性的工具调用,从架构层面保证格式不丢失。
什么是 safe-docx
safe-docx 是一个开源 TypeScript MCP Server,专为 AI 编码代理"手术式"编辑已有 Word 文档设计。核心思路:不生成脚本、不重建文档,而是通过一组原子化的工具直接操作文档。
安装配置
# Claude Code 一行安装
claude mcp add safe-docx -- npx -y @usejunior/safe-docx
# 或手动添加到 .mcp.json
{
"mcpServers": {
"safe-docx": {
"command": "npx",
"args": ["-y", "@usejunior/safe-docx"]
}
}
}
bash
四个核心工具
| 工具 | 作用 | 格式安全 |
|---|---|---|
read_file | 读取 .docx 文件,返回带稳定 ID 的段落列表 | 只读,不改任何东西 |
grep | 在文档中搜索文本模式 | 只读 |
replace_text | 替换指定文本 | 原地替换,不动格式属性 |
save | 保存编辑后的文件 | 另存为新路径 |
使用方式
装好 MCP 后,直接对 Claude Code 说:
用 safe-docx 编辑 report.docx:
1. 把 "2025年度" 改成 "2026年度"
2. 把第三章标题改成 "技术方案设计"
保留所有格式不变,另存为 report-2026.docx
markdown
Claude Code 会调用 MCP 工具链:先 read_file 了解文档结构 → grep 定位目标文本 → replace_text 原地替换 → save 输出新文件。
为什么比写 python-docx 脚本更安全
| 对比维度 | python-docx 脚本 | safe-docx MCP |
|---|---|---|
| 格式保证 | 依赖 AI 写对代码 | 工具本身设计为格式安全 |
| 出错概率 | 高(AI 可能用错 API) | 低(工具内部处理) |
| 修订追踪 | 不支持 | 支持 tracked changes |
| 批注/脚注 | 需手动处理 | 原生支持 |
| 学习成本 | 需了解 Run/Paragraph | 零代码,自然语言 |
适用场景:需要编辑已有文档且必须保留格式时,这是目前最可靠的方案。
不适用场景:从零生成新文档。safe-docx 专注"编辑已有文档",不做文档生成。
策略三:docxtpl 模板引擎方案
如果你的需求是"基于模板批量生成文档"(不是编辑已有文档),docxtpl 是 Python 生态最成熟的方案。
核心思路
在 Word 文档中直接插入 Jinja2 模板标签,Python 脚本负责填充数据。设计和逻辑完全分离——格式在 Word 里调,数据在 Python 里填。
安装
pip install docxtpl
bash
制作模板
用 Word 打开文档,在想动态替换的位置插入 Jinja2 标签:
尊敬的 {{ client_name }}:
本报告覆盖 {{ start_date }} 至 {{ end_date }} 期间的数据。
{% for item in items %}
- {{ item.name }}:{{ item.value }}
{% endfor %}
text
保存为 template.docx。
Python 填充数据
from docxtpl import DocxTemplate
# 加载模板
doc = DocxTemplate("template.docx")
# 准备数据
context = {
'client_name': '张三',
'start_date': '2025-01-01',
'end_date': '2025-12-31',
'items': [
{'name': '用户增长', 'value': '32%'},
{'name': '营收', 'value': '580万元'},
{'name': '客户满意度', 'value': '94.5分'},
]
}
# 渲染并保存 — 模板中的字体、颜色、表格样式全部保留
doc.render(context)
doc.save("report-2025.docx")
python
动态表格
# 模板中的表格行写法:
# {%tr for item in data %}
# {{ item.name }} | {{ item.qty }} | {{ item.price }}
# {%tr endfor %}
python
{%tr} 标签会按表格行循环,每行的格式(边框、底纹、对齐)继承模板行的设置。
适用场景
| 场景 | 适合 | 不适合 |
|---|---|---|
| 月度报告批量生成 | 模板固定、数据变化 | |
| 合同/协议填写 | 固定模板 + 变量替换 | |
| 编辑已有文档的特定段落 | 改不了"第3段加一句话" |
关键优势:非技术人员可以直接用 Word 修改模板样式,不需要改代码。
策略四:docxedit — 专注格式保留的 Python 库
docxedit 是一个小巧的 Python 库,专门解决 python-docx 的格式丢失问题。一句话概括:像记事本一样改文字,格式纹丝不动。
安装
pip install docxedit
bash
使用示例
import docxedit
# 替换文本 — 自动处理 Run 拆分,保留每个 Run 的独立格式
docxedit.replace_string('report.docx', '2025年度', '2026年度')
# 替换并另存
docxedit.replace_string('report.docx', '旧内容', '新内容', output_file='report-new.docx')
# 删除包含特定关键词的段落
docxedit.remove_paragraph('report.docx', '此段落需要删除')
# 在指定关键词后插入文本
docxedit.add_paragraph_after(
'report.docx',
'插入位置关键词',
'这是新插入的段落内容'
)
python
与 python-docx 的区别
# python-docx — 手动遍历 Run,容易出错
doc = Document('report.docx')
for paragraph in doc.paragraphs:
for run in paragraph.runs:
if '旧内容' in run.text:
run.text = run.text.replace('旧内容', '新内容')
# docxedit — 一行搞定,内部自动处理 Run 拆分
docxedit.replace_string('report.docx', '旧内容', '新内容')
python
核心差异:当替换的文本跨越了多个 Run(比如"2025"和"年度"分别在两个 Run 里),python-docx 需要你自己处理 Run 合并/拆分逻辑,docxedit 内部自动搞定。
适用场景
- 需要在代码中精确控制替换逻辑
- 不想装 MCP,只想要一个简单的
pip install解决方案 - 替换操作简单,不涉及复杂结构修改
策略五:加载—修改—保存(python-docx 原生方案)
最灵活但也最容易出错的方式——直接用 python-docx 写脚本。
替换指定文本
from docx import Document
doc = Document('report.docx')
old_text = '2025年度'
new_text = '2026年度'
for paragraph in doc.paragraphs:
for run in paragraph.runs:
if old_text in run.text:
run.text = run.text.replace(old_text, new_text)
# 也检查表格中的文本
for table in doc.tables:
for row in table.rows:
for cell in row.cells:
for paragraph in cell.paragraphs:
for run in paragraph.runs:
if old_text in run.text:
run.text = run.text.replace(old_text, new_text)
doc.save('report-2026.docx')
python
在 CLAUDE.md 中固定模板路径
## 文档模板
- Word 文档模板路径:`templates/reference.docx`
- 生成新文档时必须从此模板创建
markdown
reference.docx 模板方案
如果你经常让 AI 生成新文档,用模板文件统一风格:
- 打开 Word,新建空白文档
- 修改样式:"开始"→"样式"面板 → 右键样式 → "修改"
- 设置页面布局:页边距、纸张大小
- 保存为
templates/reference.docx
from docx import Document
doc = Document('templates/reference.docx')
doc.add_heading('需求分析报告', level=1) # 继承模板中 Heading 1 的样式
doc.add_paragraph('这是正文内容') # 继承模板中 Normal 的样式
doc.save('output/new-report.docx')
python
完整交付链路:从公司模板到正式 docx
前面五种策略解决的是"格式不丢"。真实交付场景往往更完整:手里有一份公司内部的示例文档(带封面、目录、类图、接口总览表、响应码说明的模板),要把项目里的 API、类图等内容按这个模板生成一份正式的 Word 交付物,还要求题注、交叉引用、域这些 Word 特性可用。直接让 AI 从模板生成 docx 是最容易翻车的做法,正确的链路是先绕一圈 Markdown。
为什么必须先走 Markdown 中转
docx 本质是一个 zip 压缩包,解压后是大量 OOXML 字符——命名空间、样式属性、关系文件。这些字符对模型来说没有语义价值,但会成倍消耗上下文窗口。把结构化内容先落在 Markdown 里,AI 的注意力全部花在内容与结构上;docx 只在最后一步"套格式"时出现。这就是上下文经济:文本格式节约上下文,把窗口留给真正的工作。
五步链路
第一步:模板转 Markdown。 把公司示例文档交给 AI,转成一份保留结构(章节层级、表格列、题注位置)的 Markdown 骨架。同时让 AI 把项目内容(接口定义、SQL、类图)填充进去。实战提示词(注意它没有角色设定,但有工具说明、相对路径上下文、操作顺序和明确的输出目录):
按照《接口文档示例.docx》的结构,把当前项目 docs/ 目录中的接口内容
整理成一份完整的 Markdown 文档,输出到 md/ 目录。
项目没有现成类图:使用 PlantUML 依据 schema/ 目录下的 SQL 生成类图,
类图 PNG 输出到 md/ 目录相对位置的 assets/ 目录中。
markdown
为什么要指定输出目录:AI 的上下文窗口有限,过程中不落盘的中间产物(图片、转换脚本)会散落在根目录或临时位置,既不方便回溯调整,也不方便统一清理。给每个中间产物一个明确的目录(md/、md/assets/),链路才是可控的。
第二步:校验 Markdown。 让 AI 自己校验一遍结构与数据(接口数量、字段与代码是否一致),确认无误再进入 docx 环节——Markdown 上改错是秒级的,docx 上改错要重跑整条链路。
第三步:复制模板编辑 docx。 这一步就是前面讲的"加载—修改—保存"思路的 skill 版(课程里的 cn-docx / docx skill):复制一份示例 docx,在副本上编辑,样式、页眉页脚、字体全部继承模板。提示词要把 Word 特有的坑显式说清:
按照《接口文档示例.docx》的格式输出新的 docx 文档:
使用 docx skill 复制一份示例文档后在其上编辑,保留原有样式;
注意图片需要交叉引用题注,域相关的代码保留并更新;
表格/图片之前补充 1-3 句业务说明,"如图 X 所示"处使用交叉引用并验证。
markdown
第四步:表格不对,用"迷你模板"修复。 实测最常见的不达标项是表格(外框线、表头加粗、底纹、列宽)。与其反复用文字描述"表格要什么样",不如做一个只含一张标准表格的迷你模板:打开示例文档,删掉其他内容只留那张表,另存为《表格模板.docx》,然后:
上面输出的文档表格格式不对。按我刚刚发的《表格模板.docx》重新输出,
注意表格的题注及交叉引用不变,同样复制模板编辑以保留格式,
不再输出 PDF,输出到根目录,并删除 md/ 目录中的 docx 与 pdf 中间产物。
markdown
第五步:细节打磨。 剩下的都是提示词能解决的小问题:某列被挤成竖排、标题没居中、数字和英文与中文之间没有空隙——把排版规则逐条写进提示词("中英文之间加一个空格""标题居中"),或者沉淀到 CLAUDE.md 里一劳永逸。最后加上封面、更新目录(全选按 F9 更新全部域),文档即可交付。
交叉引用、题注与域:python-docx 的硬核实现
Word 里的"题注自动编号"和"交叉引用"本质是域(Field):题注编号是 SEQ 域,交叉引用是指向书签的 REF 域,目录是 TOC 域。python-docx 没有封装域 API,必须手工构造 OOXML 节点。完整可运行的实现:
from docx import Document
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
from docx.enum.text import WD_ALIGN_PARAGRAPH
def add_bookmark(paragraph, name, bid):
"""在段落上打书签,供 REF 域引用"""
start = OxmlElement('w:bookmarkStart')
start.set(qn('w:id'), str(bid))
start.set(qn('w:name'), name)
end = OxmlElement('w:bookmarkEnd')
end.set(qn('w:id'), str(bid))
paragraph._p.insert(0, start)
paragraph._p.append(end)
def add_field(paragraph, instr, placeholder='1'):
"""插入域:begin / instrText / separate / 占位结果 / end"""
r = paragraph.add_run()
r._r.append(_fld('begin'))
r2 = paragraph.add_run()
t = OxmlElement('w:instrText')
t.set(qn('xml:space'), 'preserve')
t.text = f' {instr} '
r2._r.append(t)
r3 = paragraph.add_run()
r3._r.append(_fld('separate'))
paragraph.add_run(placeholder) # 域结果占位,更新域后被真实编号替换
r4 = paragraph.add_run()
r4._r.append(_fld('end'))
def _fld(kind):
f = OxmlElement('w:fldChar')
f.set(qn('w:fldCharType'), kind)
return f
doc = Document('接口文档示例.docx') # 复制模板加载,勿用 Document() 新建
# 1. 图片题注:图 { SEQ Figure \* ARABIC } 系统类图(居中、宋体小五)
cap = doc.add_paragraph()
cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
cap.add_run('图 ')
add_field(cap, r'SEQ Figure \* ARABIC')
cap.add_run(' 系统核心类图')
add_bookmark(cap, '_RefFigClass', 1001) # 书签打在题注段落上
# 2. 插入图片本体
doc.add_picture('assets/class-diagram.png')
# 3. 正文交叉引用:如图 { REF _RefFigClass \h } 所示(点击可跳转)
body = doc.add_paragraph('系统实体结构如图 ')
add_field(body, r'REF _RefFigClass \h')
body.add_run(' 所示。')
# 4. 关键一步:让 Word 打开时自动更新全部域(题注编号/交叉引用/目录一起生效)
upd = OxmlElement('w:updateFields')
upd.set(qn('w:val'), 'true')
doc.settings.element.append(upd)
doc.save('接口文档-交付.docx')
python
三个必须知道的坑:
| 坑 | 原因 | 解法 |
|---|---|---|
| 生成的域只显示占位符"1" | 域结果是 Word 打开时才计算的 | w:updateFields 让 Word 打开时提示更新全部域;或打开后全选按 F9 |
| 中文宋体、西文 Times New Roman 不生效 | python-docx 的 font.name 只管西文 | run.font.name = 'Times New Roman' 之外再设 rFonts 的 w:eastAsia 为宋体 |
| "图 1-1"这种带章节号的题注 | 单个 SEQ 域不够 | 组合域:STYLEREF 1 \s + SEQ Figure \* ARABIC \s 1,直接让 AI 按这个写 |
正文中英文混排的字体设置:
run.font.name = 'Times New Roman' # 西文
run._element.rPr.rFonts.set(qn('w:eastAsia'), '宋体') # 中文
python
这些 OOXML 细节不需要背——它们恰恰是"描述目标,让 AI 写"的最佳场景。你要做的是在提示词里把验收标准说清楚:"题注可点击跳转""打开文档时域自动更新""中文宋体小四、英文 Times New Roman"。上表的三个坑就是验收时最容易踩中的检查点。
其他 MCP 方案对比
除了 safe-docx,还有几个 Word 相关的 MCP Server,各有侧重:
| MCP Server | 语言 | 核心定位 | 格式保留 | 适合场景 |
|---|---|---|---|---|
| safe-docx | TypeScript | 编辑已有文档 | 原生支持 | 修改合同/报告中的特定文字 |
| docx-mcp | Rust | 全功能文档操作 | 部分支持 | 创建/编辑/转换 Word 文档 |
| Office-Word-MCP | Node.js | 创建+格式化 | 创建时可控 | 从零生成带格式的 Word |
| Document Generator MCP | Node.js | 生成文档 | 创建时可控 | 一句话生成 .docx 或 PDF |
选型建议:
- 编辑已有文档(改几个字、换段内容)→
safe-docx - 从模板批量生成(月报、合同)→
docxtpl - 从零创建新文档(需要控制格式)→
Office-Word-MCP或docx-mcp - 简单替换不想装任何东西→ 提示词约束 + python-docx / docxedit
实战检查清单
每次让 AI 编辑 Word 文档前,检查这三项:
- 备份原件:让 AI 输出到新文件名,不要覆盖原文件
doc.save('report-edited.docx') # 不要 save 到原文件名python - 选择合适的工具:根据场景选策略(编辑→safe-docx,生成→docxtpl,简单替换→docxedit)
- 检查输出:AI 编辑后打开文档检查三个关键点
- 标题样式是否保留(颜色、字号、字体)
- 表格是否完好(边框、对齐、列宽)
- 页面设置是否不变(页边距、页眉页脚)
常见问题速查
| 现象 | 原因 | 解决方法 |
|---|---|---|
| 字体全部变成 Calibri | 用了 Document() 创建新文档 | 改用 Document('原文件.docx') 加载 |
| 段落内部分文字加粗丢失 | 用了 paragraph.text 整段替换 | 改用 run.text 逐个替换,或用 safe-docx |
| 表格边框消失 | 表格被重新创建 | 只修改 cell.paragraphs 中的 run.text |
| 页边距变了 | 修改了 section 的 margin 属性 | 不要动 doc.sections 相关代码 |
| 新增段落样式不对 | 用了 add_paragraph() 但没指定样式 | 复制相邻段落的 style 属性 |
| 替换文本跨 Run 丢失 | 替换词被拆到多个 Run 中 | 用 docxedit 或 safe-docx |
总结
| 策略 | 适用场景 | 核心原则 | 可靠度 |
|---|---|---|---|
| 提示词约束 | 临时编辑,一两次操作 | 说清楚"加载已有文档、保留格式" | ★★☆ |
| CLAUDE.md 规则 | 频繁编辑文档的项目 | 一次配置,每次自动生效 | ★★☆ |
| safe-docx MCP | 编辑已有文档,必须保留格式 | MCP 工具从架构层保证格式安全 | ★★★ |
| docxtpl 模板引擎 | 基于模板批量生成文档 | 设计和逻辑分离,模板定义样式 | ★★★ |
| docxedit 库 | 简单文本替换 | 自动处理 Run 拆分,一行搞定 | ★★★ |
| python-docx 原生 | 需要精确控制修改范围 | 只改 run.text,不动格式属性 | ★★☆ |
| reference.docx 模板 | 经常生成新文档 | 模板定义样式,生成时继承 | ★★☆ |
| Markdown 中转 + 模板复制 | 按公司模板生成正式交付物 | 内容在 MD 上定型,docx 只做套壳 | ★★★ |
| 迷你表格模板 | 表格格式反复不达标 | 单表模板做对照,精确复刻 | ★★★ |
一句话原则:让 AI 改内容,不让 AI 碰格式。具体做法就是选对工具(safe-docx / docxtpl / docxedit),而不是靠提示词祈祷。
↑